/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement a symbol table dumper.
 *
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */

#include "../include/amcc_time.h"
#include "../include/amcc_log.h"
#include "../include/tree.h"

#include "inside/_stb_inner.h"

//  XML element's name
#define  TXT_ROOT           "SYMTABLES"
#define  TXT_TABLE          "TABLE"
#define  TXT_ENTRY_VAR      "ENTRY_VAR"
#define  TXT_ENTRY_FUN      "ENTRY_FUN"
#define  TXT_ENTRY_LAB      "ENTRY_LAB"
#define  TXT_ENTRY_ERR      "ERR_ENTRY"

// Class Symbol-Table-Dumper
typedef struct symtable_dumper {
    int  indent;         // indentations for layout
    int  depth_counter;  // depth of a sym-table node in sym-table-tree
    int  max_depth;
    int  table_count;    // counter of all sym-tables
    int  entry_count;    // counter of all sym-entries
    FILE * fp;
} t_dumper, * t_dumper_ptr ;

static void symtable_dump_impl(t_dumper_ptr dumperPtr,
                               t_symtable_ptr tablePtr,
                               t_symtable_ptr parentTablePtr);
static void dump_fileHead(t_dumper * pDumper);
static void dump_fileTail(t_dumper * pDumper);


void symtable_dump(t_symtable_ptr symtablePtr) {
    if (NULL == symtablePtr) {
        log_warn( "Hi, NO dump a null sym-table-tree  '`v_v`'" );
        return;
    }

    FILE * fp = fopen(amcc_get_symbolFilePath(), "wt");
    if (NULL == fp) {
        ERROR_REPORT_FILEOP(EC_WRITEFILE, amcc_get_symbolFilePath());
        return;
    }

    t_dumper dumper;
    memset(&dumper, 0, sizeof(dumper));
    dumper.fp = fp;

    log_info( "====> DUMPing SYMBOL-TABLE-TREE into file [%s]", amcc_get_symbolFilePath());
    dump_fileHead(&dumper);
    symtable_dump_impl(&dumper, symtablePtr, NULL);
    dump_fileTail(&dumper);
    fclose(fp);
    log_info( "====> DUMPed  SYMBOL-TABLE-TREE into file [%s],"
              " total %d %s, %d table%s, max nesting depth is %d" ,
              amcc_get_symbolFilePath(),
              dumper.entry_count,
              (dumper.entry_count > 1 ? "entries" : "entry"),
              dumper.table_count,
              (dumper.table_count > 1 ? "s" : ""),
              dumper.max_depth);
}

extern const char * cstring_toXMLstring(char * buff, const char * src);  // util/stringc.c

static void helper_dump_varEntry(t_symtable_ptr containerTablePtr,
                                 t_dumper_ptr        dumperPtr,
                                 t_symentry_ptr      pSymEntry,
                                 t_var_symInfo_ptr   pVarInfo)
{
    t_tree_ptr node = pSymEntry->definitionTreeNode;
    t_position pos = tree_get_startPosition(node);
    const char * typeString = symentry_typename(pSymEntry);

    fprintf(dumperPtr->fp, "%.*s<" TXT_ENTRY_VAR,
            dumperPtr->indent, amcc_long_spacestring());
    const char * fmt =
            "  id=\"%tx\"  treeNode=\"%tx\"  varName=\"%s\""
            "  type=\"%s\"  offset=\"%zu\"  size=\"%zu\""
            "  initializer=\"%tx\"  pos=\"%d:%d\" />\n";
    char buff[FILENAME_MAX];
    fprintf(dumperPtr->fp, fmt, pSymEntry,
            node, tree_get_markText(node),
            cstring_toXMLstring(buff, typeString),
            pVarInfo->offset,
            type_width(pVarInfo->typePtr),
            pVarInfo->initializerNode, pos.line, pos.column );
}


static void helper_dump_labelEntry(t_symtable_ptr containerTablePtr,
                                   t_dumper_ptr        dumperPtr,
                                   t_symentry_ptr       pSymEntry,
                                   t_label_symInfo_ptr pLabelInfor)
{
    t_tree_ptr      node      = pSymEntry->definitionTreeNode;
    t_position      pos       = tree_get_startPosition(node);

    fprintf(dumperPtr->fp, "%.*s<" TXT_ENTRY_LAB,
            dumperPtr->indent, amcc_long_spacestring());
    const char * fmt =  "  id=\"%tx\"  treeNode=\"%tx\""
                        "  labelName=\"%s\"  pos=\"%d:%d\" />\n";


    fprintf(dumperPtr->fp, fmt,
            pSymEntry, node, tree_get_markText(node),
            pos.line, pos.column );
}

static void helper_dump_funcEntry(t_symtable_ptr containerTablePtr,
                                  t_dumper_ptr        dumperPtr,
                                  t_symentry_ptr      pSymEntry,
                                  t_func_symInfo_ptr  pFuncInfo)
{
    t_tree_ptr      node    = pSymEntry->definitionTreeNode;
    t_position      pos     = tree_get_startPosition(node);
    const char * typeString = symentry_typename(pSymEntry);

    fprintf(dumperPtr->fp, "%.*s<" TXT_ENTRY_FUN,
            dumperPtr->indent, amcc_long_spacestring());
    const char * fmt = "  id=\"%tx\"  treeNode=\"%tx\""
           "  funcName=\"%s\"  type=\"%s\"  pos=\"%d:%d\" >\n";
    char buff[FILENAME_MAX];
    fprintf(dumperPtr->fp, fmt, pSymEntry, node,
            tree_get_markText(node),
            cstring_toXMLstring(buff, typeString),
            pos.line, pos.column );
    // dump symtable of function
    dumperPtr->indent += 2;
    symtable_dump_impl(dumperPtr, pFuncInfo->symTablePtr, containerTablePtr);
    dumperPtr->indent -= 2;

    fprintf(dumperPtr->fp, "%.*s</" TXT_ENTRY_FUN ">\n",
            dumperPtr->indent, amcc_long_spacestring());
}

struct dump_data {
    t_dumper_ptr    dumperPtr;
    t_symtable_ptr  containerTablePtr;
};

static int callback_dump_entry(t_map_key key, t_map_value value, void* user_data) {
    struct dump_data * dumpData = (struct dump_data *)user_data;
    t_dumper_ptr    dumperPtr   = dumpData->dumperPtr;
    t_symentry_ptr  pSymEntry   = (t_symentry_ptr) value;
    t_tree_ptr      node        = pSymEntry->definitionTreeNode;
    t_position      pos         = tree_get_startPosition(node);

    ++(dumperPtr->entry_count);
    if (tree_is_funcNameDefinition(node) ) {
        helper_dump_funcEntry(dumpData->containerTablePtr,
                              dumperPtr,    pSymEntry,
                              pSymEntry->content.funcInfo);
    }
    else if (tree_is_varNameDefinition(node)) {
        helper_dump_varEntry(dumpData->containerTablePtr,
                             dumperPtr,     pSymEntry,
                             pSymEntry->content.varInfo);
    }
    else if (tree_is_labelNameDefinition(node)) {
        helper_dump_labelEntry(dumpData->containerTablePtr,
                               dumperPtr,   pSymEntry,
                               pSymEntry->content.labelInfo);
    }
    else {
        fprintf(dumperPtr->fp, "%.*s<" TXT_ENTRY_ERR,
                dumperPtr->indent, amcc_long_spacestring());
        char buff[FILENAME_MAX];
        fprintf(dumperPtr->fp,
                "  id=\"%tx\"  treeNode=\"%tx\"  kind=\"%s\"  "
                "UNKNOWN_marker=\"%s\"  pos=\"%d:%d\"\n",
                (ptrdiff_t)pSymEntry, (ptrdiff_t)node,
                token_nameOfKind( tree_get_tokenKind(node)),
                cstring_toXMLstring(buff, tree_get_markText(node)),
                pos.line, pos.column);
    }

    return 0;
}

static void symtable_dump_entries(t_dumper_ptr dumperPtr,
                                  t_hashmap_ptr mapPtr,
                                  t_symtable_ptr containerTablePtr)
{
    struct dump_data  userdata;
    userdata.containerTablePtr = containerTablePtr;
    userdata.dumperPtr = dumperPtr;

    hashmap_foreach(mapPtr, callback_dump_entry, &userdata );
}


static void dump_fileHead(t_dumper * pDumper) {
    FILE* fp = pDumper->fp;
    fprintf(fp, "<?xml version=\"1.0\"  encoding=\"GBK\"?>\n");
    fprintf(fp, "<?xml-model href=\"" XML_XSD_SYMTBL "\" ?>\n");
    const char * nowTimeTxt = amcc_get_timestring(NULL);
    char buff[FILENAME_MAX+1];
    fprintf(fp, "<" TXT_ROOT
            " xmlns=\"" XML_NAMESPACE_SYMTBL "\""
            " generator=\"" AMCC_PRJ_NAME "\"  infile=\"%s\""
            " ctime=\"%s\" >\n",
            cstring_toXMLstring(buff, amcc_get_inputFilePath()),
            nowTimeTxt);
}


static void dump_fileTail(t_dumper * pDumper) {
    FILE* fp = pDumper->fp;
    fprintf(fp,  "</" TXT_ROOT ">\n<!-- ");
    fprintf(fp, "total %d %s, %d table%s, max nesting depth is %d",
            pDumper->entry_count,
            (pDumper->entry_count > 1 ? "entries" : "entry"),
            pDumper->table_count,
            (pDumper->table_count > 1 ? "s" : ""),
            pDumper->max_depth);
    fprintf(fp,  " -->\n");
}


static void symtable_dump_impl(t_dumper_ptr dumperPtr,
                               t_symtable_ptr tablePtr,
                               t_symtable_ptr parentTablePtr)
{
    t_tree_ptr  treeNode = tablePtr->ownerTreeNode;
    bool isLeaf = tree_is_leaf(tablePtr->ownerTreeNode);  // func-name, may be
    if (isLeaf) treeNode = tree_get_parent(treeNode);
    t_position posStart = tree_get_startPosition(treeNode);
    t_position posStop  = tree_get_stopPosition(treeNode);

    dumperPtr->table_count += 1;
    ++(dumperPtr->depth_counter);
    if ((dumperPtr->depth_counter) > dumperPtr->max_depth )
        dumperPtr->max_depth = dumperPtr->depth_counter;

    // dump general information
    fprintf(dumperPtr->fp, "%.*s<" TXT_TABLE,
            dumperPtr->indent, amcc_long_spacestring());
    const char * fmt = "  id=\"%tx\" parent=\"%tx\"  depth=\"%d\""
                       "  treeNode=\"%tx\"  scope=\"%s,%s\""
                       "  entries=\"%d\"  varSize=\"%zu\"  blocks=\"%d\""
                       "  pos1=\"%d:%d\" pos2=\"%d:%d\" >\n";

    fprintf(dumperPtr->fp, fmt, tablePtr, parentTablePtr,
            dumperPtr->depth_counter, tablePtr->ownerTreeNode,
            isLeaf ? "func" : "NT",
            tree_get_markText(tablePtr->ownerTreeNode),
            hashmap_size( tablePtr->localNames ),
            tablePtr->totalSizeOfVariables,
            list_size(&(tablePtr->blockTables)),
            posStart.line, posStart.column,
            posStop.line, posStop.column );

    dumperPtr->indent += 2;

    // dump entries
    symtable_dump_entries(dumperPtr, tablePtr->localNames, tablePtr);

    // dump sub-tables
    t_list_iterator  itr;
    LIST_ITERATOR_INIT( &itr,  &(tablePtr->blockTables) );
    t_list_node_ptr subTablePtr = LIST_ITERATOR_BEGIN( &itr );
    t_list_node_ptr end         = LIST_ITERATOR_END  ( &itr );
    for ( ; subTablePtr != end;  subTablePtr = LIST_ITERATOR_NEXT( &itr ) ) {
        symtable_dump_impl (dumperPtr,
                            (t_symtable_ptr)list_node_get_value(subTablePtr),
                            tablePtr);
    }

    dumperPtr->indent -= 2;

    int nEntries = hashmap_size(tablePtr->localNames);
    fprintf(dumperPtr->fp,
            "%.*s</" TXT_TABLE ">"
            "<!-- END of sub-TABLE[%tx] of TABLE[%tx] (%d %s) -->\n",
            dumperPtr->indent, amcc_long_spacestring(),
            (ptrdiff_t)tablePtr,    (ptrdiff_t)parentTablePtr,
            nEntries, (nEntries>1 ? "entries" : "entry") );

    --(dumperPtr->depth_counter);
}
